package com.zipking.cloud.springbootmybatis.controller;

import java.math.BigDecimal;

import cn.hutool.core.date.StopWatch;
import com.alibaba.excel.util.ListUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zipking.cloud.springbootmybatis.config.Async;
import com.zipking.cloud.springbootmybatis.model.*;
import com.zipking.cloud.springbootmybatis.enums.PayChannelEnum;
import com.zipking.cloud.springbootmybatis.persistance.entity.MsgLog;
import com.zipking.cloud.springbootmybatis.persistance.mapper.MsgLogMapper;
import com.zipking.cloud.springbootmybatis.redislock.DistributedLock;
import com.zipking.cloud.springbootmybatis.redislock2.RedisLock;
import com.zipking.cloud.springbootmybatis.service.BatchQueryPageUtilsService;
import com.zipking.cloud.springbootmybatis.service.IMsgLogService;
import com.zipking.cloud.springbootmybatis.service.MultiThreadQueryUtil;
import com.zipking.cloud.springbootmybatis.service.ThreadTableDataQuery;
import com.zipking.cloud.springbootmybatis.service.impl.OperatorTask;
import com.zipking.cloud.springbootmybatis.service.impl.QueryUsersTask;
import com.zipking.cloud.springbootmybatis.strategy.AbstractStrategyChoose;
import com.zipking.cloud.springbootmybatis.util.BatchUtils;
import com.zipking.cloud.springbootmybatis.util.IdWorkerUtils;
import com.zipking.cloud.springbootmybatis.util.ThreadPoolUtils;
import com.zipking.cloud.springbootmybatis.util.excel.EasyExcelUtil;
import com.zipking.cloud.springbootmybatis.util.excel.EasyExcelUtilExt;
import com.zipking.cloud.springbootmybatis.util.excel.ExcelWriter;
import com.zipking.cloud.springbootmybatis.util.excel.Query;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("/")
public class SystemCntroller {
    @Autowired
    AbstractStrategyChoose abstractStrategyChoose;
    @Autowired
    private ThreadPoolUtils threadPoolUtils;
    @Autowired
    private IMsgLogService msgLogService;
    @Autowired
    private MsgLogMapper msgLogMapper;
    @Autowired
    private ThreadTableDataQuery threadTableDataQuery;
    @Autowired
    private ThreadPoolTaskExecutor queryBatchPool;
    @Autowired
    private BatchQueryPageUtilsService batchQueryPageUtilsService;
    @GetMapping("/ok")
    public String queryList() {
        return "ok";
    }

    /**
     * 多线程查询方式1
     *
     * @return
     */
    @GetMapping("/testQuery1")
    public Object testQuery1() {
        List<MsgLog> msgLogList = Lists.newArrayList();
        for (int i = 0; i < 3000000; i++) {
            MsgLog msgLog = new MsgLog();
            msgLog.setMsgId(IdWorkerUtils.getInstance().nextId() + "");
            msgLog.setMsg("XX");
            msgLog.setExchange("XX");
            msgLog.setRoutingKey("XX");
            msgLog.setStatus(0);
            msgLog.setTryCount(0);
            msgLogList.add(msgLog);

        }
        log.info("组装完成");
        msgLogService.jdbcBatchInser(msgLogList);
        //   List<MsgLog> msgLogList = msgLogService.list();

        return "OK";
    }
    Logger logger = LoggerFactory.getLogger("xx");
    /**
     * 多线程查询方式2
     *
     * @return
     */
    @GetMapping("/testQuery2")
    public Object testQuery2(HttpServletResponse response) throws Exception {
        /*PageReq pageReq=new PageReq();
        pageReq.setPageIndex(1);
        pageReq.setPageRows(10);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(10,
                10,
                10L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(200), new ThreadPoolExecutor.CallerRunsPolicy());

        batchQueryPageUtilsService.batchProcess(poolExecutor,10,200,"XXX",pageReq,logger,null);
        List<MsgLog> dataListByBatch = msgLogService.getDataListByBatch();*/
        List<MsgLog> list = msgLogService.logicHandlerExt(new HashMap<>());
        //   List<MsgLog> msgLogList = msgLogService.list();
        //EasyExcelUtil.writeExcel(response, list, "订单", MsgLog.class);
        return "OK";
    }

    /**
     * 多线程查询方式3
     *
     * @return
     */
    @GetMapping("/testQuery3")
    public Object testQuery3() throws Exception {
        StopWatch s = new StopWatch();
        s.start();
        LambdaQueryWrapper<MsgLog> queryWrapper = new LambdaQueryWrapper<MsgLog>();
        queryWrapper.eq(MsgLog::getRoutingKey, "XX");
        int totalNum = msgLogService.count(queryWrapper);
        //重写多线程中引用的方法
        List<MsgLog> threadHandle = threadPoolUtils.threadAsync(totalNum, new ThreadTableDataQuery() {
            @Override
            public Future<List> selectTableDataAsync(Integer page, Integer limit) throws Exception {
                return threadTableDataQuery.selectTableDataAsync(page, limit);
            }
        });
        s.stop();
        log.info(threadHandle.size() + "条数据花费时间 : " + s.getTotalTimeSeconds() + " s");
        return threadHandle.size();
    }


    /**
     * 多线程查询方式4
     *
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/exportCountOrderInfo", method = RequestMethod.GET)
    public void exportCountOrderInfo(HttpServletResponse response) throws IOException {


        int pageSizeWrite = 100000;
        int pageSize = 10000;
        //其他非分页sql数据查询

        //long totalCount=orderInfoService.listCountOrderInfo(orderInfoStatisticsSearch).getTotal();
        Long start = System.currentTimeMillis() / 1000;
        //分布式锁，60s后过期
      /*  if (!redisLoader.setIfAbsent("saas:exportData:exportCountOrderInfo", start+"", 60 * 1000)) {
            log.info("接口在执行!!,不能重复执行");
            return;
        }*/
        try {
           /* log.info(orderInfoStatisticsSearch.getTenantCode() + ",orderInfo/exportCountOrderInfo,startTime:{},endTime:{},count:{}",
                    orderInfoStatisticsSearch.getStartTime(), orderInfoStatisticsSearch.getEndTime(), orderInfoStatisticsSearch.getCount());*/

            //禁止使用mybatis的分页查询，mybatis的分页查询会先进行count查询，导出查2次数据
            //总数
            LambdaQueryWrapper<MsgLog> queryWrapper = new LambdaQueryWrapper<MsgLog>();
            queryWrapper.eq(MsgLog::getRoutingKey, "XX");
            int totalNum = msgLogService.count(queryWrapper);
            //可以取到小数点后的正确数值，如果两个都是整形，那小数点后面就会清零
            float numberA = Long.valueOf(totalNum);
            //总页数
            float numberB = pageSize;
            //计算实际读取数据时分页大小
            Integer pagesFact = (int) Math.ceil(numberA / numberB);
            //计算写入数据时分页大小
            float numberC = pageSizeWrite;
            int pagesWrite = (int) Math.ceil(numberA / numberC);

            log.info("计算写入页码用时：" + (System.currentTimeMillis() / 1000 - start) + "秒,{},总数:{}", pagesWrite, totalNum);
            //log.info("条件：{}", JSONObject.toJSONString(orderInfoStatisticsSearch));


            for (int i = 0; i < pagesWrite; i++) {
                //每百万条数据切换一个sheet
                /*WriteSheet writeSheet = sheets.get(i / 10);
                long start1 = System.currentTimeMillis() / 1000;*/
                long start1 = System.currentTimeMillis() / 1000;


                //主线程等待子线程执行完毕
                List<CompletableFuture<List<MsgLog>>> futureList = new ArrayList<>();
                // List<MsgLog> listSearch = new ArrayList<>();


                log.info(System.currentTimeMillis() + ":阻塞异步开始");

                //pageSizeWrite/pageSize 写入数据分页大小和读取数据分页大小的倍率
                for (int j = 1; j <= pageSizeWrite / pageSize; j++) {
                    //增加异步查询的次数，来减少每次查询的时间，削峰填谷
                    int k = i * pageSizeWrite / pageSize + j;
                    if (k <= pagesFact) {
                        //并发线程内需要变更的对象需要在线程内创建创建新对象处理
                        //log.warn("读取页数:{},页大小:{}",k ,pageSize);
                        //log.info("读取页数条件：{},id", orderInfoStatisticsSearch.getOrderId()!=null?orderInfoStatisticsSearch.getOrderId():null);
                        CompletableFuture<List<MsgLog>> job1 = msgLogService.getExportDataList(j, pageSize);
                        futureList.add(job1);
                    }
                }
                List<MsgLog> list = new ArrayList<>();
                for (CompletableFuture<List<MsgLog>> listCompletableFuture : futureList) {
                    List<MsgLog> join = listCompletableFuture.get();
                    if (join.size() > 0) {
                        list.addAll(join);
                        join.clear();
                    }
                }

                log.info(System.currentTimeMillis() + ":异步阻塞结束");
                log.warn("读取数据耗时：{}", System.currentTimeMillis() / 1000 - start1);
                List<MsgLog> newList = new ArrayList<>();
                if (list.size() > 0) {
                    System.out.println("总共:" + list.size());

                }
                //gc，清空内存
                list.clear();
                log.warn("pageNo：,{},pageSizeWrite:{},pagesWrite{},耗时：{}", i, pageSizeWrite, pagesWrite, System.currentTimeMillis() / 1000 - start1);
            }

            long end = System.currentTimeMillis() / 1000;
            log.info("订单报表导出耗时：" + (end - start) + "秒");
          /*  if (String.valueOf(start).equals(redisLoader.get("saas:exportData:exportCountOrderInfo"))) {
                //删锁
                redisLoader.delete("saas:exportData:exportCountOrderInfo");
            }*/

        } catch (Exception e) {
            log.error("导出订单失败", e);
        } finally {

        }

    }


    //批量导出数据到excel
    @GetMapping(value = "/batchExport")
    public void batchExport(HttpServletResponse response) {

        msgLogService.exportExcel(response);

    }

    @GetMapping(value = "/batchExportExt")
    public void batchExportExt(HttpServletResponse response) {

        EasyExcelUtilExt.ExcelParam p = EasyExcelUtilExt.ExcelParam.builder()
                .baseMapper(msgLogService.getBaseMapper())           // 传Service类的BaseMapper进去
                .lambdaQueryWrapper(new LambdaQueryWrapper<MsgLog>() // 构建查询条件
                        .eq(true, MsgLog::getRoutingKey, "XX")
                )
                .pageNo(1)                     // 数据从第一行开始，如果表头只有一行就是1，表头有两行的话就写2
                .respClazz(MsgLog.class) // 这里写实体类
                .pageSize(10000)                // Excel每页数据大小
                .fileName("test")              // 文件名
                .sheetName("测试sheet")        // 页名
                .build();

        EasyExcelUtilExt.exportExcel(p, response);


    }

    /**
     * 多线程查询方式5
     *
     * @return
     */
    @ResponseBody
    @RequestMapping(value = "/getBacth", method = RequestMethod.GET)
    public Object getBacth(HttpServletResponse response) throws Exception {
        msgLogService.asyncBatchInit();
        //EasyExcelUtil.writeExcel(response, dataListByBatch, "订单", MsgLog.class);
        return "OK";
    }

    /**
     * 多线程查询方式6
     *
     * @return
     */
    @GetMapping("/testQuery6")
    public Object testQuery6() {

        List<MsgLog> msgLogList = msgLogService.queryAllUseThreadPool(50000);
        //   List<MsgLog> msgLogList = msgLogService.list();
        return msgLogList.size();
    }


    @Autowired
    private QueryUsersTask queryUsersTask;
    @Autowired
    private OperatorTask operatorTask;

    @GetMapping("/trans")
    public Object trans() {
        TaskWrapper<MsgLog> taskWrapper = new TaskWrapper<>();
        Async.startSingleWhile(queryUsersTask, taskWrapper);
        return "任务启动成功";
    }


    @GetMapping("/query1")
    public Object query1() {
        //msgLogService.asyncBatchInit();
        LambdaQueryWrapper<MsgLog> wrapper = new LambdaQueryWrapper();
        wrapper.select(MsgLog::getMsgId).last(" LIMIT 1895");
        List<String> ids = this.msgLogService.list(wrapper).stream().map(MsgLog::getMsgId).collect(Collectors.toList());
        List<MsgLog> result = BatchUtils.protectBach(ids, idList -> {
            return this.msgLogService.lambdaQuery().in(MsgLog::getMsgId, idList).list();
        });
        return result;
    }

    @Autowired
    private MultiThreadQueryUtil multiThreadQueryUtil;

    @GetMapping("/query2")
    public Object query2() {
        //msgLogService.asyncBatchInit();
        List multiCombineResult = multiThreadQueryUtil.getMultiCombineResult();
        return multiCombineResult.size();
    }


    @GetMapping("/testResultHandler")
    public Object testResultHandler(HttpServletResponse response) {
        //msgLogService.asyncBatchInit();
        // msgLogService.testResultHandler();

        com.zipking.cloud.springbootmybatis.util.excel.ExcelWriter writer = com.zipking.cloud.springbootmybatis.util.excel.ExcelWriter.create();
        try {
            writer.setResponse(response, "测试")
                    .setHead(ListUtils.newArrayList("测试", "姓名"))
                    .builder()
                    .multiThread(10)
                    .writer(new Query<List<List<MsgLog>>>() {
                        @Override
                        public List<List<MsgLog>> andThen() {
                            List<MsgLog> data = new ArrayList<>();
                            for (int i = 0; i < 30000; i++) {
                                MsgLog msgLog = new MsgLog();
                                msgLog.setMsgId("1" + i);
                                msgLog.setMsg("1" + i);
                                msgLog.setExchange("1" + i);
                                msgLog.setRoutingKey("1" + i);
                                msgLog.setStatus(0);
                                msgLog.setTryCount(0);
                                msgLog.setNextTryTime(new Date());
                                msgLog.setCreateTime(new Date());
                                msgLog.setUpdateTime(new Date());
                                data.add(msgLog);
                            }
                            List<List<MsgLog>> res = new ArrayList<>();
                            res.add(data);
                            return res;
                        }
                    });
        } catch (Exception e) {
            try {
                ExcelWriter.error(response, e.getMessage());
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
            throw new RuntimeException(e);
        } finally {
            writer.close();
        }
        return "oK";
    }


    //批量导出数据到excel
    @GetMapping(value = "/batchExport8")
    public void batchExport8(HttpServletResponse response) {

        msgLogService.exportExcel8(response);

    }


    @GetMapping("/editSupplierInfo")
    //@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")
    @DistributedLock(key = "#dto.sku", lockTime = 600L, keyPrefix = "sku-", fair = true, tryLok = true)
    public Object editSupplierInfo(ProductSkuSupplierInfoDTO dto) {
        return "OK";
    }

    @RedisLock(value = "#id", isEl = true, waitTime = 1, leaseTime = 100)
    @GetMapping("/listTest")
    public Object listTest(@RequestParam("id") Long id) throws Exception {
        PayCallbackCommand payCallbackCommand = new PayCallbackCommand();
        payCallbackCommand.setChannel(PayChannelEnum.ALI_PAY.getCode());
        payCallbackCommand.setOrderRequestId(IdWorkerUtils.getInstance().createUUID());
        payCallbackCommand.setGmtPayment(new Date());
        PayCallbackRequest payCallbackRequest = PayCallbackRequestConvert.command2PayCallbackRequest(payCallbackCommand);
        PayCommand requestParam = new PayCommand();
        requestParam.setOutOrderSn("");
        requestParam.setTotalAmount(new BigDecimal("0"));
        requestParam.setSubject("");
        requestParam.setTradeType(0);
        requestParam.setOrderSn("");
        requestParam.setChannel(0);
        requestParam.setOrderRequestId("");

        // abstractStrategyChoose.chooseAndExecute(payCallbackRequest.buildMark(), payCallbackRequest);
        PayRequest payRequest = PayRequestConvert.command2PayRequest(requestParam);
        PayResponse result = abstractStrategyChoose.chooseAndExecuteResp("ALI_PAY_NATIVE", payRequest);

        System.out.println("=====方法执行中");
        // Thread.sleep(150000);
        System.out.println("=====方法执行完成");
        return "OK";
    }

    int pageSize = 100;

    private int getNumberOfPages(Integer count) {
        return (int) Math.ceil(count / (double) pageSize);
    }

    @GetMapping(value = "/doUpdate1")
    public Object doUpdate1() throws Exception {
        LambdaQueryWrapper<MsgLog> queryWrapper = new LambdaQueryWrapper<MsgLog>();
        queryWrapper.eq(MsgLog::getRoutingKey, "XX");
        int total = msgLogMapper.selectCount(queryWrapper);
        int numberOfPages = getNumberOfPages(total);
        List<Future<List<MsgLog>>> futures = Lists.newArrayList();
        //int offset = 0;
        for (int i = 1; i <=numberOfPages; i++) {
            int page = i;
            Map<@Nullable String, @Nullable Object> map = Maps.newHashMap();
            map.put("limit", (page-1)*pageSize);
            map.put("num", pageSize);
           // offset = offset + pageSize;
            Future<List<MsgLog>> future = queryBatchPool.submit(() -> {
                List<MsgLog> users = msgLogMapper.selectListByWhere(map);

                return Lists.newArrayList(users);
            });
            futures.add(future);
        }
        System.out.println(" ====================================== ");
        List<MsgLog> entitiesToUpdate = new ArrayList<MsgLog>();
        for (Future<List<MsgLog>> future : futures) {
            try {
                entitiesToUpdate.addAll(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        // 批量更新到其他表
        //otherRepository.batchUpdate(entitiesToUpdate);
        return entitiesToUpdate.size();
    }


    /**
     * 方式5
     * @return
     */
    @RequestMapping("query4")
    public int executeThreadPool() {
        List<MsgLog> list = new ArrayList<MsgLog>();
        //计算表总数
        LambdaQueryWrapper<MsgLog> queryWrapper = new LambdaQueryWrapper<MsgLog>();
        queryWrapper.eq(MsgLog::getRoutingKey, "XX");
        int totalCount = msgLogMapper.selectCount(queryWrapper);
        int totalPages = getNumberOfPages(totalCount);
        ExecutorService executor = Executors.newFixedThreadPool(totalPages);

        CountDownLatch latch = new CountDownLatch(totalPages);
        for (int i = 1; i <= totalPages; i++) {
            Map<@Nullable String, @Nullable Object> map = Maps.newHashMap();
            map.put("pageNo", (i-1)*pageSize);
            map.put("pageSize", pageSize);
            executor.submit(() -> {
                try {
                    List<MsgLog> list_ = msgLogMapper.selectListByWhereExt(map);
                    list.addAll(list_);
                } finally {
                    latch.countDown();
                }
            });
        }

        try {
            latch.await(); // 等待所有查询任务完成
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 处理中断异常
        } finally {
            executor.shutdown();
        }
        return list.size();
    }

}
